
// TODO: 模板编译
import parsePath from '../data/parsePath'
import Watcher from '../data/Watcher'

export default class Complier {
    constructor(el, vue) {
        // 1 挂载vue实例
        this.$vue = vue
        // 2 挂载dom节点
        this.$el = document.querySelector(el)
        // 3 有挂载点
        if (this.$el) {
            // 4 调用函数，让节点变为fragment
            let $fragment = this.nodeToFragment(this.$el)
            // 5 编译解析节点
            this.complie($fragment)
            // 6 上树
            this.$el.appendChild($fragment)
        }
    }

    // TODO: 通过 mustache语法 转换为AST，再通过diff算法转换成文档碎片，这里非研究内容，只做简单展示
    nodeToFragment (el) {
        let frag = document.createDocumentFragment()
        let child
        while (child = el.firstChild) {
            frag.appendChild(child)
        }
        return frag
    }

    // TODO: 编译文档碎片
    complie (el) {
        // 1 得到子元素
        let childNodes = el.childNodes
        // 2 类型判断对应编译函数
        let reg = /\{\{(.*?)\}\}/g
        // 3 解析文档碎片，进行赋值
        childNodes.forEach(node => {
            let text = node.textContent || ''
            // 判断类型
            if (node.nodeType === 1) {
                this.complieElement(node)
            }
            else if (node.nodeType === 3 && reg.test(text)) {
                // 获取文本内容
                let keys = text.match(reg)
                this.complieText(node, keys)
            }
        })
    }
    // TODO：编译 类型为1 的元素节点
    complieElement (node) {
        // 1 获取属性列表
        let attrs = Array.prototype.slice.call(node.attributes)
        // 2 分析指令
        attrs.forEach(attr => {
            let attrName = attr.name
            let arrrVal = attr.value
            // 判断是否是指令
            if (attrName.indexOf('v-') === 0) {
                // 指令都是以 v 开头
                let dir = attrName.substring(2)
                this.analyze(dir, arrrVal, node)
            }
        })
    }
    // TODO：编译 类型为3 的文本节点
    complieText (node, keys) {
        // 1 保存更新前值
        let originTxt = node.textContent
        // 2 更新节点
        this.updateNode(node, originTxt, keys)
        // 3 设置正则解析值
        let reg = /\{\{\s?(.*?)\s?\}\}/
        // 4 遍历每一个文本键值
        keys.forEach(key => {
            // 1 去除空格，获取键值
            key = key.match(reg)[1]
            // 2 进行监听
            new Watcher(this.$vue, key, newV => {
                // 更新节点
                this.updateNode(node, originTxt, keys)
            })
        })
    }
    // TODO：节点解析值进行更新
    updateNode (node, oldTxt, keys) {
        // 1 设置正则解析值
        let reg = /\{\{\s?(.*?)\s?\}\}/
        // 2 遍历每一个文本键值
        keys.forEach(key => {
            // 1 去除空格，获取键值
            key = key.match(reg)[1]
            // 2 防止有深层嵌套对象值
            let value = this.getDeepVal(this.$vue, key)
            // 3 如果 value值为空
            if (!value) {
                // 1 为每一个key前面加上 'this.'
                key = 'return ' + key.replace(/\w+?/g, (v) => {
                    return 'this.' + v
                })
                // 2 使用new funcition
                value = new Function('', key).call(this.$vue.$data)
            }
            // 4 逐步替换值
            oldTxt = oldTxt.replace(reg, value)
        })
        // 3 节点进行替换
        node.textContent = oldTxt
    }

    // TODO: 分析指令，进行操作
    analyze (name, val, node) {
        switch (name) {
            case 'module':
                // 1 获取值，先绑定上
                let value = this.getDeepVal(this.$vue, val)
                node.value = value
                // 2 进行监听
                new Watcher(this.$vue, val, newV => {
                    node.value = newV
                })
                // 3 双向绑定需要添加监听
                node.addEventListener('input', e => {
                    let newV = e.target.value
                    // 更改值
                    this.setDeepVal(newV, val)
                })
                break
            case 'if':
                // 1 获取值
                let if_val = this.getDeepVal(this.$vue, val)
                // 2 判断结果
                let bool = Boolean(if_val)
                // 3 决定是否显示，if是移除节点
                !bool && node.parentNode.removeChild(node)
                // 4 新添订阅函数
                new Watcher(this.$vue, val, newV => {
                    !Boolean(newV) && node.parentNode.removeChild(node)
                })
                break
        }
    }

    // TODO：获取深层嵌套对象值
    getDeepVal (data, exp) {
        return parsePath(exp)(data)
    }

    // TODO：设置深层嵌套对象值
    setDeepVal (val, exp) {
        let data = this.$vue
        exp = exp.split('.')
        let len = exp.length - 1
        let target
        exp.forEach((key, i) => {
            if (i === len) {
                if (target) {
                    target[key] = val
                }
                else {
                    data[key] = val
                }
            }
            else {
                target = data[key]
            }
        })
    }
}
